package 反射;

import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/*
    Java反射机制概述
        Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法
        加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息,可以通过这个对象看到类的结构,这个对象就像一面镜子,透过这个镜子看到类的结构,所以形象的称之为:反射
             正常方式:引入需要的"包类"名称  --> 通过new实例化 --> 取得实例化对象
             反射方式:实例化对象 --> getClass()方法 --> 得到完整的"包类"名称

    补充:动态语言 vs 静态语言
         1、动态语言
              是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化,通俗点说就是在运行时代码可以根据某些条件改变自身结构
              主要的动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang
         2、静态语言
             与动态语言相对应的,运行时结构不可变的语言就是静态语言,如Java、C、 C++
        注:Java不是动态语言,但Java可以称之为"准动态语言",即Java有一定的动态性,可以利用反射机制、字节码操作获得类似动态语言的特性,Java的动态性让编程的时候更加灵活

    Java反射机制提供的功能
        在运行时判断任意一个对象所属的类
        在运行时构造任意一个类的对象
        在运行时判断任意一个类所具有的成员变量和方法
        在运行时获取泛型信息
        在运行时调用任意一个对象的成员变量和方法
        在运行时处理注解
        生成动态代理

    反射相关的主要API
         java.lang.Class:代表一个类
         java.lang.reflect.Method:代表类的方法
         java.lang.reflect.Field:代表类的成员变量
         java.lang.reflect.Constructor:代表类的构造器

    通过直接new的方式或反射方式都可以调用公共结构,开发中到使用哪个?
        正常情况下都使用直接new的方式,反射用于在编译时无法确定要造哪个对象时使用
    反射机制与面向对象中的封装性是否矛盾?如何看待两个技术?
        不矛盾,封装体现在建议上,如一个属性设置为了private,则代表该属性不建议直接调用,可以通过get()/set()或其他方法进行调用,且更好,而
        反射则体现在能不能去调用的问题,能调不代表一定需要去调
 */
public class Reflection01 {

    /*
        一般情况下对Person类的操作:
            在Person类外部,不可以通过Person类的对象调用其内部私有结构
            如:name、showNation()以及私有的构造器
     */
    @Test
    public void test1(){
        //创建Person类的对象
        Person person = new Person("Tom",12);
        //通过对象,调用其内部的属性和方法
        person.age = 10;
        System.out.println(person.toString());
        person.show();
    }

    /*
        使用反射实现与上面同等的操作
     */
    @Test
    public void test2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        Class<Person> personClass = Person.class;
        //调用指定的构造器(第一个参数类型为String,第二个参数类型为int)
        Constructor<Person> personConstructor = personClass.getConstructor(String.class, int.class);
        //通过构造器创建对象  --> 基于反射
        Person person = personConstructor.newInstance("Tom", 12);
        System.out.println(person.toString());

        //通过反射调用对象指定的属性
        Field age = personClass.getDeclaredField("age");
        //将该属性重新赋值
        age.set(person,10);
        System.out.println(person.toString());

        //通过反射调用对象指定的方法
        Method show = personClass.getDeclaredMethod("show");
        show.invoke(person);

        /*
            通过反射,调用Person类的私有结构,如私有的构造器、方法、属性等
         */
        Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor(String.class); //调用私有的构造器
        declaredConstructor.setAccessible(true);
        Person selfPerson = declaredConstructor.newInstance("Jerry");
        System.out.println(selfPerson.toString());

        //通过反射调用私有的属性
        Field name = personClass.getDeclaredField("name");
        name.setAccessible(true);
        name.set(selfPerson,"Mike");
        System.out.println(selfPerson.toString());

        //通过反射调用私有的方法
        Method showNation = personClass.getDeclaredMethod("showNation", String.class);//调用该方法需要参数
        showNation.setAccessible(true);
        String nation = (String)showNation.invoke(selfPerson, "中国");//相当于String nation = selfPerson.showNation("中国");
        System.out.println(nation);
    }
}

class Person{

    private String name;
    public int age;

    public Person() {
        System.out.println("调用Person类空参构造器");
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    //定义三个方法
    private Person(String name){
        this.name = name;
    }

    public void show(){
        System.out.println("show()方法调用");
    }

    private String showNation(String nation){
        System.out.println("我的国籍是:" + nation);
        return nation;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
